/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package fourbox.client;

import clientGUI.ClientLoginThread;
import fourbox.ClientProtocol;
import fourbox.ProtocolMessage;
import fourbox.FUtil;
import fourbox.Metafile;
import fourbox.WatchDir;
import fourbox.server.Server;
import java.io.*;
import java.net.Socket;
import java.nio.file.Paths;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sound.midi.SysexMessage;

/**
 *
 * @author hartono
 */
public class Client implements Runnable {

    public static final int CLIENT_DOWNLOAD_PORT = 13546;
    public static final String FOURBOX_CONFIG_FILE = "config.txt";
    public List sendingQueue = null;
    public List receivingQueue = null;
    public String username = "";
    public ClientDownloader downloader;
    public Thread clientDownloaderThread;
    public WatchDir monitor;
    public Thread clientMonitor;
    public boolean connectionOn = false;
    public boolean login = false;
    public boolean wait = false;
    public boolean waitUploadDaemon = false;
    public boolean waitUploadNonDaemon = false;
    public boolean receiveUpdate = false;
    public File workingDir = null;
    public double lastTime;
    public long lastTimeRegularlyList;
    public double currTime;
    public String DEFAULT_WORK_DIR = "D:/FourBox/";
    public Thread fileSenderThread = null;
    public ClientUploader sender = null;
    private static final double TIME_OUT = 10000;
    
    public String host;
    
     ObjectOutputStream out ;
     public ClientLoginThread clientLogin = null;
    public Thread loginThread;
    public synchronized void doLogout() throws InterruptedException{
        try {
            out.writeInt(1);
            out.write(ProtocolMessage.CLIENT_MSG_LOGOUT);
            out.flush();
            
            Thread.sleep(500);
        } catch (IOException ex) {
            Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
        }
        
    }
    
    public boolean waitLogin = true;

    public synchronized void doLogin(ObjectOutputStream out) throws IOException {
        Scanner stdIn= new Scanner(System.in) ;
//        BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
        String username = "",
                password = "";
//        String []inputs;
//        String input;
//        input = stdIn.nextLine();
//        inputs = input.split("-") ;
//        System.out.println("input "+input);
        
        username = stdIn.nextLine();
        password = stdIn.nextLine();
//
//        System.out.println("Insert your username and password!");
//        System.out.print("username : ");
//        username = stdIn.readLine();
//        System.out.print("passwrod : ");
//        password = stdIn.readLine();
        
        

        this.username = username;

        int usize = username.length();
        int psize = password.length();

        byte[] user = FUtil.usernameToByteArray(username);
        byte[] pass =null;
        try {
            pass = FUtil.md5enc(password).getBytes();
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
        }

//        debug
        System.out.println("user " + FUtil.byteArrayToString(user));
//        System.out.println("pass " + FUtil.byteArrayToString(pass));

        out.writeInt(58);
        out.write(ProtocolMessage.CLIENT_MSG_LOGIN);
        out.write(usize);
        out.write(pass.length);
        out.write(user);
        out.write(pass);
        out.flush();
    }
    
    public synchronized void doLogin(String username, String password) throws IOException {
//        Scanner stdIn= new Scanner(System.in) ;
//        BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
//        String username = "",
//                password = "";
//        String []inputs;
//        String input;
//        input = stdIn.nextLine();
//        inputs = input.split("-") ;
//        System.out.println("input "+input);
        
//        username = stdIn.nextLine();
//        password = stdIn.nextLine();
//
//        System.out.println("Insert your username and password!");
//        System.out.print("username : ");
//        username = stdIn.readLine();
//        System.out.print("passwrod : ");
//        password = stdIn.readLine();
        
        

        this.username = username;

        int usize = username.length();
        int psize = password.length();

        byte[] user = FUtil.usernameToByteArray(username);
        byte[] pass =null;
        try {
            pass = FUtil.md5enc(password).getBytes();
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
        }

//        debug
        System.out.println("user " + FUtil.byteArrayToString(user));
//        System.out.println("pass " + FUtil.byteArrayToString(pass));
        System.out.println("pass " + password);
        
        out.writeInt(58);
        out.write(ProtocolMessage.CLIENT_MSG_LOGIN);
        out.write(usize);
        out.write(pass.length);
        out.write(user);
        out.write(pass);
        out.flush();
        System.out.println("end");
        waitLogin=false;
    }

    public static void main(String args[]) throws IOException {
        Client client = new Client();
        client.initClient();

        Thread clientThread = new Thread(client);
        clientThread.start();

    }

    @Override
    public void run() {
        try {
            // initialize downloader        
            sendingQueue = Collections.synchronizedList(new ArrayList());
            lastTime = System.currentTimeMillis();
            lastTimeRegularlyList = System.currentTimeMillis();
            ClientProtocol protocol = new ClientProtocol();

            Socket socket;
            while (true) {
                try {
                    socket = new Socket(host, Server.SERVER_LISTENING_PORT);
                    connectionOn=true;
                    break;
                } catch (Exception e) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException ex) {
                        Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
            // debug
            System.out.println("Client : socket is created");

             out = new ObjectOutputStream(socket.getOutputStream());
            ObjectInputStream in = new ObjectInputStream(socket.getInputStream());

            while (connectionOn) {
                currTime = System.currentTimeMillis();
                if (in.available() != 0) {
                    wait = false;
                    // get message
                    int len = in.readInt(); // len of message
                    byte[] response = new byte[len]; // message response
                    in.read(response); // read response                

                    // debug
                    //                    System.out.println("Client : response = " + FUtil.byteArrayToString(response));

                    // proses response
                    int resultCode = protocol.clientProcess(response);

                    // debug
                    //                    System.out.println("Client : result code = " + resultCode);

                    // efek response dilakukan
                    if (resultCode == ClientProtocol.LOGIN_SUCCESS) {
                        System.out.println("Client : login success");
                        login = true;
                        connectionOn = true;
                        lastTime = System.currentTimeMillis();

                        //                        monitor = new ClientMonitor();
                        //                        clientMonitor = new Thread(monitor);
                        //                        clientMonitor.start();
                        monitor = new WatchDir(Paths.get(workingDir.toString()), true);
                        clientMonitor = new Thread(monitor);
                        clientMonitor.start();

                        receivingQueue = Collections.synchronizedList(new ArrayList());
                        downloader = new ClientDownloader(receivingQueue, workingDir.toString());
                        clientDownloaderThread = new Thread(downloader);
                        clientDownloaderThread.start();


                    } else if (resultCode == ClientProtocol.LOGIN_FAILED) {
                        System.out.println("Client : login failed");
                        connectionOn = false;
                        this.username = "";
                    } else if (resultCode == ClientProtocol.START_CONNECTED) {
                        System.out.println("Client : connected to server, try to login");
//                        clientLogin = new ClientLoginThread(this);
//                         loginThread = new Thread(clientLogin);
//                        loginThread.start();
//                        try {
//                            loginThread.join();
//                        } catch (InterruptedException ex) {
//                            Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
//                           
//                            loginThread.stop();
//                        }
                          while (waitLogin){                              
                            try {
                                Thread.sleep(10);
                            } catch (InterruptedException ex) {
                                Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
                            }
                          }
                        System.out.println("Client : wait for ack");
                        wait = true;
                        lastTime = System.currentTimeMillis();
                        connectionOn = true;
                    } else if (resultCode == ClientProtocol.CONNECTION_ALIVE) {
                        //                        System.out.println("Client : connection still alive");
                        connectionOn = true;
                        wait = false;
                        lastTime = System.currentTimeMillis();
                    } else if (resultCode == ClientProtocol.THERE_ARE_UPDATE) {
                        // ada perubahan yang harus dilakukan, baca metafile dari input stream,
                        // kemudian nyalakan thread client downloader 
                        receiveUpdate = true;
                        System.out.println("Client : There are update to do");
                        int size = response[1];
                        synchronized (receivingQueue) {
                            for (int i = 0; i < size; i++) {
                                Metafile m;
                                try {
                                    m = (Metafile) in.readObject();
                                    receivingQueue.add(m);
                                } catch (ClassNotFoundException ex) {
                                    Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
                                }

                            }
                        }
                        lastTime = System.currentTimeMillis();

                    } else if (resultCode == ClientProtocol.SHOULD_COMMIT) {
                        // harus ngirim perubahan ke server, dari client
                        int size = response[1];
                        System.out.println("Client : Should commit some files");
                        synchronized (sendingQueue) {
                            if (size > 0 && !waitUploadNonDaemon) {
                                for (int i = 0; i < size; i++) {
                                    try {
                                        Metafile m = (Metafile) in.readObject();
                                        System.out.println("Client : " + m + " should be committed to server");
                                        sendingQueue.add(m);
                                    } catch (ClassNotFoundException ex) {
                                        Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
                                    }
                                }
                                Iterator it;
                                out.writeInt(2);                                        // len
                                out.write(ProtocolMessage.CLIENT_MSG_LIST_FILES_CHANGES);                   // id                     
                                out.write(sendingQueue.size());// object count
                                it = sendingQueue.iterator();
                                while (it.hasNext()) {
                                    Metafile m = (Metafile) it.next();
                                    out.writeObject(m);
                                    System.out.println("Sent message about " + m);
                                }
                                out.flush();

                                try {
                                    Thread.sleep(1000);
                                } catch (InterruptedException ex) {
                                    Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
                                }

                                //                                    if (sender == null) {
                                sender = new ClientUploader(workingDir.toString(), "localhost", Server.SERVER_DOWNLOAD_PORT, sendingQueue);
                                //                                    }

                                if (sender != null) {
                                    fileSenderThread = new Thread(sender);
                                    fileSenderThread.start();
                                    //                                try {
                                    //                                    fileSenderThread.join();
                                    //                                } catch (InterruptedException ex) {
                                    //                                    Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
                                    //                                }
                                    //                                                     
                                    waitUploadNonDaemon = true;
                                }
                                lastTime = System.currentTimeMillis();
                            }
                        }

                    }
                } else {
                    // ga ada input masuk, berarti kita ngecek apa ada yang berubah, kalo                    
                    if (!wait) {
                        //                        System.out.println("Not wiat");
                        // not wait for ack                        

                        // if thread monitor mengirim pesan, 
                        // maka ditanganin
                        //               


                        if (monitor != null) {
                            List diffs = monitor.getQueue();

                            if (!receiveUpdate) {
                                if (diffs.size() > 0) {
                                    sendingQueue.clear();
                                    System.out.println("Client : [MONITOR effect]About to sent " + diffs.size() + " messages");
                                    Iterator it;
                                    synchronized (diffs) {
                                        it = diffs.iterator();
                                        while (it.hasNext()) {
                                            Metafile m = (Metafile) it.next();
                                            sendingQueue.add(m);
                                        }
                                        diffs.clear();
                                    }

                                    // sent message list files yang berubah
                                    out.writeInt(2);                                        // len
                                    out.write(ProtocolMessage.CLIENT_MSG_LIST_FILES_CHANGES);                   // id                     
                                    out.write(sendingQueue.size());// object count
                                    it = sendingQueue.iterator();
                                    while (it.hasNext()) {
                                        Metafile m = (Metafile) it.next();
                                        out.writeObject(m);
                                        System.out.println("Client : [MONITOR effect] Sent message about " + m);
                                    }
                                    out.flush();

                                    try {
                                        Thread.sleep(1000);
                                    } catch (InterruptedException ex) {
                                        Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
                                    }

                                    //                                    if (sender == null) {
                                    sender = new ClientUploader(workingDir.toString(), "localhost", Server.SERVER_DOWNLOAD_PORT, sendingQueue);
                                    //                                    }

                                    if (sender != null) {
                                        fileSenderThread = new Thread(sender);
                                        fileSenderThread.start();
                                        //                                        try {
                                        //                                            fileSenderThread.join();
                                        //                                        } catch (InterruptedException ex) {
                                        //                                            Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
                                        //                                        }
                                        //                                                     
                                    }
                                    lastTime = System.currentTimeMillis();
                                }// size >0
                            } else {
                                // jangan dulu
                                //                                System.out.println("Jangan dulu monitor watch dir");                                                                
                                if (receivingQueue.size() > 0) {
                                    if (diffs.size() > 0) {
                                        //                                        System.out.println("Jangan dulu monitor");
                                        diffs.clear();
                                    }
                                } else {
                                    receiveUpdate = false;
                                    //                                    System.out.println("SOk monitor on");
                                }
                            }// aaa

                        }// not wait for daemon


                        // menunggu hingga thread upload selesai baru bisa upload lagi
                        if (fileSenderThread != null) {
                            //                            System.out.println("Client : Waiting for uploader to finish ...");
                            if (fileSenderThread.isAlive()) {
                                //                                System.out.println("Sender is alive");
                                waitUploadDaemon = true;
                            } else {
                                //                                System.out.println("Client : Waiting stop.. waitUpload = false");
                                waitUploadDaemon = false;

                            }
                        }

                        // regularly
                        // akan dilakukan
                        // output pesan message yang berisi metafile yang berubah untuk update ke server
                        // dicek di server
                        // 
                        if (currTime - lastTimeRegularlyList > 5000) {
                            //                            System.out.println("Working dir " + workingDir.toString() + " , " + workingDir.toPath());
                            System.out.println("Client : [MONITOR off] Sent regularly list messages");
                            ProtocolMessage.createListFilesMsgClient(workingDir.toString() + "/", workingDir.toPath(), out);
                            lastTimeRegularlyList = System.currentTimeMillis();
                            lastTime = System.currentTimeMillis();
                        }

                        //                        if (receivingQueue.size() == 0) {
                        //                            try {
                        //                                Thread.sleep(100);
                        //                                List diffs = monitor.getQueue();
                        //                                synchronized (diffs) {
                        //                                    diffs.clear();
                        //                                }
                        //                                receiveUpdate = false;
                        //                            } catch (InterruptedException ex) {
                        //                                Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
                        //                            }
                        //                            System.out.println("Receive update = false");
                        //                        }

                        // saatnya mengirim keep alive, harus nunggu ack
                        if (currTime - lastTime > TIME_OUT) {
                            out.writeInt(1);
                            out.write(ProtocolMessage.CLIENT_MSG_KEEP_ALIVE);
                            out.flush();
                            wait = true;
                            //                            System.out.println("Client : keep alive");
                            lastTime = System.currentTimeMillis();
                        }

                    } else {
                        // wait for ack                        
                        if (currTime - lastTime > TIME_OUT) {
                            System.out.println("Client : Timeout curr: " + currTime + ", last : " + lastTime);
                            // TIME OUT
                            connectionOn = false;
                        }
                    }

                }
            }
            // closing socket, client connection off
            in.close();
            out.close();
            socket.close();
            System.out.println("Client : socket is closed");
        } catch (IOException ex) {
            Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
        }


    }

    public void changeWorkDir(File dir) throws IOException {
        if (dir.equals(workingDir)) {
        } else {
            FUtil.move(workingDir.toPath(), dir.toPath());
            setWorkingDir(dir);
            File configFile = new File(FOURBOX_CONFIG_FILE);
            if (configFile.exists()){
                configFile.delete();
            }
            configFile.createNewFile();
            // set working directory            
            FileWriter writer = new FileWriter(configFile);
            // default working directory
            writer.write("WORKDIR=" + workingDir.getAbsolutePath() + "\n");
            writer.write("HOST=" + host+ "\n");
            writer.close();
        }
    }

    private void setWorkingDir(File dir) throws IOException {
        if (!dir.exists()) {
            dir.mkdir();
            File conf = new File(dir + "/.fourbox");
            workingDir = dir;
            conf.createNewFile();

        } else {
            File conf = new File(dir + "/.fourbox");
            if (conf.exists()) {
            } else {
                // uda pernah move
            }
            workingDir = dir;
        }
        System.out.println("Client : Set working directory");
    }

    public void initClient() throws IOException {
        System.out.println("Client : Initializing ...");
        initConfig();        
    }

    private void initConfig() throws IOException {
        System.out.println("Client : Init Config");
        File configFile = new File(FOURBOX_CONFIG_FILE);
        if (!configFile.exists()) {
            // new installed
            createFileConfig();
            System.out.println("Client : New Config created");
        } else {
            // uda ada file config
            // read            
            BufferedReader reader = new BufferedReader(new FileReader(configFile));

            String workingPath = reader.readLine();
            String[] workingPaths = workingPath.split("=");
            setWorkingDir(new File(workingPaths[1]));
            String hosts = reader.readLine();
            String host = (hosts.split("="))[1] ;
            this.host = host;
            // set working dir

            System.out.println("Client : Config read");
            System.out.println("Working dir " + workingDir);
            System.out.println("Host "+this.host);
        }
    }

    private void createFileConfig() throws IOException {
        File configFile = new File(FOURBOX_CONFIG_FILE);
        configFile.createNewFile();
        // set working directory
        setWorkingDir(new File(DEFAULT_WORK_DIR));
        host = "localhost";
        FileWriter writer = new FileWriter(configFile);
        // default working directory
        writer.write("WORKDIR=" + DEFAULT_WORK_DIR + "\n");
        writer.write("HOST=" + "localhost" + "\n");
        writer.close();

    }
}
